﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using CustomNetworking;
using BB;
using System.Timers;
using System.IO;

namespace ServerConsole
{
    class BoggleServer
    {
        private TcpListener server;
        private Queue<ClientConnection> connections;

        private string customBoard = "";

        public int activeGames { get; private set; }

        private int gameTime;

        private static HashSet<string> dictionary;

        public BoggleServer(int time, string path)
        {
            dictionary = LoadDictionary(path);
            server = new TcpListener(IPAddress.Any, 2000);
            connections = new Queue<ClientConnection>();
            gameTime = time;
            activeGames = 0;

            server.Start();
            server.BeginAcceptSocket(ConnectionRequested, null);
        }

        public BoggleServer(int time, string path, string board)
        {
            dictionary = LoadDictionary(path);
            server = new TcpListener(IPAddress.Any, 2000);
            connections = new Queue<ClientConnection>();
            gameTime = time;
            activeGames = 0;
            customBoard = board;

            server.Start();
            server.BeginAcceptSocket(ConnectionRequested, null);
        }

        private HashSet<string> LoadDictionary(string filename)
        {
            HashSet<string> set = new HashSet<string>();

            using (StreamReader reader = new StreamReader(filename))
            {
                string line;
                while ((line = reader.ReadLine()) != null)
                {
                    set.Add(line);
                }
            }

            return set;
        }

        public void StopServer()
        {
            server.Stop();
        }

        public void ConnectionRequested(IAsyncResult result)
        {
            StringSocket s = new StringSocket(server.EndAcceptSocket(result), new UTF8Encoding());
            s.BeginReceive(NewConnectionCallback, s);
            server.BeginAcceptSocket(ConnectionRequested, null);
        }

        public void NewConnectionCallback(string s, Exception e, object payload)
        {
            string s1 = s.ToUpper();
            StringSocket ss = (StringSocket)payload;

            if (s1.Substring(0, 5).Equals("PLAY "))
            {
                string name = s.Substring(5);
                connections.Enqueue(new ClientConnection(name, ss));
                PairConnections();
            }
            else
            {
                ss.BeginSend("IGNORING " + s + "\n", (x, y) => { }, null);
            }
        }

        public void PairConnections()
        {
            if (connections.Count > 1)
            {
                ClientConnection c1 = connections.Dequeue();
                ClientConnection c2 = connections.Dequeue();

                BoggleGame bg;

                if (customBoard.Length != 16)
                {
                    bg = new BoggleGame(c1, c2, gameTime, dictionary);
                }
                else
                {
                    bg = new BoggleGame(c1, c2, gameTime, dictionary, customBoard);
                }
                bg.StartGame();
                activeGames++;
            }
        }
    }

    class ClientConnection
    {
        public string Name { get; private set; }
        public StringSocket SS { get; private set; }
        public List<string> ProposedWords { get; private set; }
        public HashSet<string> LegalWords { get; private set; }
        public HashSet<string> IlegalWords { get; private set; }

        public ClientConnection(string name, StringSocket socket)
        {
            this.Name = name;
            this.SS = socket;
            ProposedWords = new List<string>();
            LegalWords = new HashSet<string>();
            IlegalWords = new HashSet<string>();
        }
    }

    class BoggleGame
    {
        private BoggleBoard bb;
        private ClientConnection player1, player2;
        private int timeRemaining;
        private int score1, score2;
        private Timer gameTimer;
        private static HashSet<string> dictionary;

        public BoggleGame(ClientConnection c1, ClientConnection c2, int time, HashSet<string> dict)
        {
            dictionary = dict;
            player1 = c1;
            player2 = c2;
            timeRemaining = time;
            bb = new BoggleBoard();
            score1 = 0;
            score2 = 0;
        }

        public BoggleGame(ClientConnection c1, ClientConnection c2, int time, HashSet<string> dict, string board)
        {
            dictionary = dict;
            player1 = c1;
            player2 = c2;
            timeRemaining = time;
            bb = new BoggleBoard(board);
            score1 = 0;
            score2 = 0;
        }

        public void StartGame()
        {
            string startString = "START " + bb.ToString() + " " + timeRemaining + " ";
            player1.SS.BeginSend(startString + player2.Name + "\n", (x, y) => { }, null);
            player2.SS.BeginSend(startString + player1.Name + "\n", (x, y) => { }, null);
            player1.SS.BeginReceive(BoggleReceiveCallback, player1);
            player2.SS.BeginReceive(BoggleReceiveCallback, player2);
            gameTimer = new Timer(1000);
            gameTimer.Elapsed += new ElapsedEventHandler(UpdateTimer);
            gameTimer.Start();
        }

        private void BoggleReceiveCallback(string s, Exception e, object p)
        {
            ClientConnection player = (ClientConnection)p;
            string upperCaseString = s.ToUpper();
            if (upperCaseString.Substring(0, 5).Equals("WORD ") && timeRemaining > 0)
            {
                player.ProposedWords.Add(upperCaseString.Substring(5));

                if (bb.CanBeFormed(upperCaseString.Substring(5)))
                {
                    player.LegalWords.Add(upperCaseString.Substring(5));
                }
                else
                {
                    player.IlegalWords.Add(upperCaseString.Substring(5));
                }

                CalcAndSendScore();
            }

            player.SS.BeginReceive(BoggleReceiveCallback, p);
        }


        private void CalcAndSendScore()
        {
            HashSet<string> p1Set = new HashSet<string>(player1.LegalWords);
            HashSet<string> p2Set = new HashSet<string>(player2.LegalWords);

            p1Set.ExceptWith(player2.LegalWords);
            p2Set.ExceptWith(player1.LegalWords);

            score1 = 0;
            foreach (string s in p1Set)
            {
                if (s.Length > 2 & s.Length < 5) score1 += 1;
                if (s.Length == 5) score1 += 2;                
                if (s.Length == 6) score1 += 3;
                if (s.Length == 7) score1 += 5;
                if (s.Length > 7) score1 += 11;
            }

            score2 = 0;
            foreach (string s in p2Set)
            {
                if (s.Length > 2 & s.Length < 5) score2 += 1;
                if (s.Length == 5) score2 += 2;
                if (s.Length == 6) score2 += 3;
                if (s.Length == 7) score2 += 5;
                if (s.Length > 7) score2 += 11;
            }

            score1 = score1 - player1.IlegalWords.Count;
            score2 = score2 - player2.IlegalWords.Count;

            player1.SS.BeginSend("SCORE " + score1 + " " + score2 + "\n", (x, y) => { }, null);
            player2.SS.BeginSend("SCORE " + score2 + " " + score1 + "\n", (x, y) => { }, null);
        }


        private void CalcAndSendSummary()
        {
            HashSet<string> p1Set = new HashSet<string>(player1.LegalWords);
            HashSet<string> p2Set = new HashSet<string>(player2.LegalWords);
            HashSet<string> common = new HashSet<string>(player1.LegalWords);

            common.UnionWith(player2.LegalWords);

            p1Set.ExceptWith(player2.LegalWords);
            p2Set.ExceptWith(player1.LegalWords);

            string p1Msg = "STOP ";
            string p2Msg = "STOP ";

            p1Msg += p1Set.Count + " ";
            foreach (string s in p1Set)
            {
                p1Msg += s + " ";
            }

            p1Msg += p2Set.Count + " ";
            foreach (string s in p2Set)
            {
                p1Msg += s + " ";
            }

            p1Msg += common.Count + " ";
            foreach (string s in common)
            {
                p1Msg += s + " ";
            }

            p1Msg += player1.IlegalWords.Count + " ";
            foreach (string s in player1.IlegalWords)
            {
                p1Msg += s + " ";
            }

            p1Msg += player2.IlegalWords.Count + " ";
            foreach (string s in player2.IlegalWords)
            {
                p1Msg += s + " ";
            }

            p2Msg += p2Set.Count + " ";
            foreach (string s in p1Set)
            {
                p2Msg += s + " ";
            }

            p2Msg += p1Set.Count + " ";
            foreach (string s in p1Set)
            {
                p2Msg += s + " ";
            }

            p2Msg += common.Count + " ";
            foreach (string s in common)
            {
                p2Msg += s + " ";
            }

            p2Msg += player2.IlegalWords.Count + " ";
            foreach (string s in player2.IlegalWords)
            {
                p2Msg += s + " ";
            }

            p2Msg += player1.IlegalWords.Count + " ";
            foreach (string s in player1.IlegalWords)
            {
                p2Msg += s + " ";
            }

            player1.SS.BeginSend("SCORE " + score1 + " " + score2 + "\n", (x, y) => { }, null);
            player2.SS.BeginSend("SCORE " + score2 + " " + score1 + "\n", (x, y) => { }, null);
        }

        private void UpdateTimer(object source, ElapsedEventArgs e)
        {
            this.timeRemaining--;
            if (timeRemaining > 0)
            {
                System.Diagnostics.Debug.WriteLine(timeRemaining);
                player1.SS.BeginSend("TIME " + timeRemaining.ToString() + "\n", (x, y) => { }, null);
                player2.SS.BeginSend("TIME " + timeRemaining.ToString() + "\n", (x, y) => { }, null);
            }
            else
            {
                CalcAndSendScore();
                //CalcAndSendSummary();
            }
        }
    }
}